package com.cooper.scraper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import com.cooper.android.comm.LRULinkedHashMap;
import com.cooper.android.comm.LRULinkedHashMap.RemoveEldestEntryListener;
import com.cooper.scraper.CustomFixExecutorService.ThreadPoolStatusListener;

import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class ThreadPoolDownloadPublishRunner implements
		RemoveEldestEntryListener<String, String>, DownloadPublishRunner {

	public static final int MESSAGE_DOWNLOAD_COMPLETE = 1;
	public static final int MESSAGE_DOWNLOAD_FAIL = 2;
	public static final int MESSAGE_DOWNLOAD_CANCEL = 3;

	private static final String BITMAP_CACHE_INFO_FILE_NAME = "bitmap_cache.ser";

	protected DownloadParams params;
	private LRULinkedHashMap<String, String> bitmapCache = null;

	// private RunnerStatusListener statusListener;

	// private Map<String, Future<?>> requests = new HashMap<String,
	// Future<?>>();

	// private ExecutorService threadPool;

	private List<RunnerSubscribe> subscribes = new ArrayList<RunnerSubscribe>();

	protected ThreadPoolDownloadPublishRunner() {
		super();
		params = new DownloadParams();
		init();
	}

	protected ThreadPoolDownloadPublishRunner(DownloadParams params) {
		super();
		this.params = params;
		init();
	}

	private void init() {
		if (params.getPageBufferSize() > 0)
			params.setPageBuffer(new LRULinkedHashMap<String, String>(params
					.getPageBufferSize()));
		initBitmapBuffer();
		params.setHandler(handler);
	}

	@SuppressWarnings("unchecked")
	private void initBitmapBuffer() {
		String cacheDirPath = params.getBitmapCacheDir();
		if (params.getBitmapBufferSize() > 0) {
			if (params.isSaveBitmapBufferToFile()) {
				try {
					FileInputStream istream = new FileInputStream(cacheDirPath
							+ "/" + BITMAP_CACHE_INFO_FILE_NAME);
					try {
						ObjectInputStream in = new ObjectInputStream(istream);
						int serMapSize = in.readInt();
						if (serMapSize == params.getBitmapBufferSize()) {
							bitmapCache = (LRULinkedHashMap<String, String>) in
									.readObject();
							Log.d("Downloader", "load cache frome file");
						}
					} finally {
						istream.close();
					}

				} catch (Exception e) {
					Log.w("Downloader", e.getMessage(), e);
				}
			}
			if (bitmapCache == null) {
				bitmapCache = new LRULinkedHashMap<String, String>(
						params.getBitmapBufferSize());
			}
			bitmapCache.setRemoveEldestEntryListener(this);
			params.setBitmapBuffer(bitmapCache);

			File cacheDir = new File(cacheDirPath);
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")
						&& cacheFile.isFile()
						&& !bitmapCache.values().contains(cacheFile.getName())) {
					cacheFile.delete();
				}
			}
		} else {
			File cacheDir = new File(cacheDirPath);
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")) {
					cacheFile.delete();
				}
			}
		}
	}

	public DownloadParams getParams() {
		return params;
	}

	private Handler handler = new Handler() {

		@Override
		public synchronized void handleMessage(Message msg) {
			List<RunnerSubscribe> tempSubscribes = new ArrayList<RunnerSubscribe>(
					subscribes);
			Set<String> keys;
			switch (msg.what) {
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_COMPLETE:
				keys = params.completeKeys();
				for (String key : keys) {
					Object resultData = params.getResult(key);
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadComplete(key, resultData);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader",
										"cancel subscribe" + e.getMessage(), e);
							}
						}
					}
				}
				break;
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_FAIL:
				keys = params.failKeys();
				for (String key : keys) {
					Integer errorCode = params.failErrorCode(key);
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadFail(key, errorCode);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_CANCEL:
				keys = params.cancelKeys();
				for (String key : keys) {
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadCancel(key);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			}

			CustomFixExecutorService.getInstance().cleanRequest();
		}
	};

	// private boolean busy = false;

	protected void run(String key, RunnerThread thread) {
		CustomFixExecutorService.getInstance().run(key, thread);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#setStatusListener(com.cooper.scraper
	 * .RunnerStatusListener)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#setStatusListener(com.cooper.scraper.CustomFixExecutorService.ThreadPoolStatusListener)
	 */
	@Override
	public void setStatusListener(ThreadPoolStatusListener statusListener) {
		CustomFixExecutorService.getInstance()
				.setStatusListener(statusListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#subscribed(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#subscribed(com.cooper.scraper.RunnerSubscribe)
	 */
	@Override
	public boolean subscribed(RunnerSubscribe subscribe) {
		return subscribes.contains(subscribe);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#subscribe(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#subscribe(com.cooper.scraper.RunnerSubscribe)
	 */
	@Override
	public void subscribe(RunnerSubscribe subscribe) {
		if (!subscribes.contains(subscribe)) {
			subscribes.remove(subscribe);
		}
		subscribes.add(subscribe);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#getTaskCount(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#getTaskCount(com.cooper.scraper.RunnerSubscribe)
	 */
	@Override
	public int getTaskCount(RunnerSubscribe subscribe) {
		CustomFixExecutorService.getInstance().cleanRequest();
		int result = 0;
		for (String key : CustomFixExecutorService.getInstance().keySet()) {
			if (subscribe.subscribed(key)) {
				result++;
			}
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.RunnerSubscribe)
	 */
	@Override
	public void cancelSubscribe(RunnerSubscribe subscribe) {
		cancelSubscribe(subscribe, true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe, boolean)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.RunnerSubscribe, boolean)
	 */
	@Override
	public void cancelSubscribe(RunnerSubscribe subscribe,
			boolean cancelDownload) {
		if (subscribes.contains(subscribe)) {
			if (cancelDownload) {
				cancel(subscribe);
			}
			subscribes.remove(subscribe);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel(java.lang.String)
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#cancel(java.lang.String)
	 */
	@Override
	public void cancel(String key) {
		CustomFixExecutorService.getInstance().cancel(key);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel()
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#cancel()
	 */
	@Override
	public void cancel() {
		CustomFixExecutorService.getInstance().cancel();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#cancel(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	/* (non-Javadoc)
	 * @see com.cooper.scraper.PublishRunner#cancel(com.cooper.scraper.RunnerSubscribe)
	 */
	@Override
	public void cancel(RunnerSubscribe subscribe) {
		CustomFixExecutorService.getInstance().cleanRequest();
		List<String> cancelRequests = new ArrayList<String>();
		for (String key : CustomFixExecutorService.getInstance().keySet()) {
			if (subscribe.subscribed(key)) {
				cancelRequests.add(key);
			}
		}
		for (String key : cancelRequests) {
			CustomFixExecutorService.getInstance().cancel(key, false);
			Log.d("DownloaderPublishRunner",
					"cancel Download from cancel subscribe key:" + key);
		}

	}

	public void downloadBitmap(final String url, String key, final float width,
			final float height) {
		downloadBitmap(url, null, key, width, height);
	}

	public void downloadBitmap(final String url, final String referer,
			String key, final float width, final float height) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = null;
					Bitmap srcImg = httpDownload.downloadBitmap(url, referer);
					if (srcImg != null) {
						result = Bitmap.createScaledBitmap(srcImg, (int) width,
								(int) height, true);
						if (srcImg != null && !srcImg.isRecycled()) {
							srcImg.recycle();
						}
					}
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	@Override
	public void downloadBitmap(final String url, String key) {
		downloadBitmap(url, null, key);
	}

	@Override
	public void downloadBitmap(final String url, final String referer,
			String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = httpDownload.downloadBitmap(url, referer);
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	@Override
	public void downloadFile(final String url, final File file, String key) {
		downloadFile(url, null, file, key);
	}

	@Override
	public void downloadFile(final String url, final String referer,
			final File file, String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					httpDownload.downloadFile(url, referer, file);
					complete(useKey, file.getPath());
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	public void destroy() {
		subscribes.clear();
		cancel();
		if (bitmapCache != null && params.isSaveBitmapBufferToFile()) {
			bitmapCache.setRemoveEldestEntryListener(null);
			String cachePath = params.getBitmapCacheDir();
			for (Entry<String, String> cache : bitmapCache.entrySet()) {
				File cacheBitmapFile = new File(cachePath + "/"
						+ cache.getValue());
				if (!cacheBitmapFile.exists()) {
					bitmapCache.remove(cache.getKey());
				}
			}
			String serFilePath = cachePath + "/" + BITMAP_CACHE_INFO_FILE_NAME;
			File serFile = new File(serFilePath);
			if (serFile.exists()) {
				serFile.delete();
			}
			try {
				FileOutputStream outstream = new FileOutputStream(serFilePath);
				try {
					ObjectOutputStream out = new ObjectOutputStream(outstream);
					out.writeInt(params.getBitmapBufferSize());
					out.writeObject(bitmapCache);
					out.flush();
				} finally {
					outstream.close();
				}
			} catch (IOException e) {
				Log.e("Downloader", e.getMessage(), e);
			}
		} else {
			File cacheDir = new File(params.getBitmapCacheDir());
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")
						&& cacheFile.isFile()) {
					cacheFile.delete();
				}
			}
		}
	}

	@Override
	public boolean onRemove(Entry<String, String> eldest) {
		File cacheFile = new File(params.getBitmapCacheDir() + "/"
				+ eldest.getValue());
		if (cacheFile.exists()) {
			cacheFile.delete();
		}
		return true;
	}
}
